% !Mode:: "TeX:UTF-8"

\chapter{饿了么LSTM神经网络预测}
本项目为《软件工程综合实践》课程的创新AI项目。本功能实现了基于LSTM长短时记忆神经网络模型的外卖送达时间预测。
使用大规模数据集进行训练模型，通过调用用户及商户地址、外卖员基本信息等参数进行送达时间预测。
\section{需求分析}


\subsection{简介}

本项目是《软件工程综合实践》课程的创新AI项目。利用数据挖掘平台Kaggle的外卖订单数据集Food Delivery Dataset
进行机器学习模型建立，并通过对应的controler层接口调用对应的模型文件，从后端数据库中读取所需参数进行送餐时间预测。、
项目涉及对于数据集的清洗、处理，以及前后端对应接口实现、数据库建立、机器模型选择、调参等多个方面，不仅需要学习
对应的机器学习相关知识，还需要对整体项目有较为清晰的把握，项目综合性较高，也具有一定的实用价值。

\subsubsection{背景}\label{subsection:PR3beijing}
在外卖软件中，预计送达时间往往对用户的体验至关重要，当用户点击商家页面准备下单时，软件应基于一定信息给出预计送达时间预测，
防止送餐时间与用户预期不匹配而导致的用户体验下降。基于由Kaggle网站提供的外卖订单数据集，外卖送餐时间与下面几个参数有关：
\begin{itemize}
    \item 商家的经纬度坐标、送餐地址的经纬度坐标（由于本项目不涉及外卖员地址，假设所有外卖员都在商家店铺中接单）
    \item 外卖员评分、外卖员年龄、使用交通工具类型、运输食物的种类
    \item 送餐路程的交通情况、天气情况、以及是否是节假日（高峰时段）
    \item 送餐所在城市
\end{itemize}
用于机器学习模型训练的参数选择应该基于该项目的数据库、以及与送餐时间存在显著相关性的参数

\section{软件设计}
\subsection{数据集处理}
该数据集包含20个属性，具体属性如表~\ref{tab:table1}~所示。
\begin{table}[htbp]
    \caption{训练数据集数据统计}\label{tab:table1}
    \vspace{0.5em}\centering\wuhao
    \begin{tabular}{cccc}
    \toprule[1.5pt]
    $Column$ & $Non-Null$ & $Count$ & $Dtype$\\
    \midrule[1pt]
    ID      & 45593 & non-null & object\\
    Delivery\_person\_ID           &45593 &non-null  &object \\
    Delivery\_person\_Age          &43739 &non-null  &float64\\
    Delivery\_person\_Ratings      &43685 &non-null  &float64\\
    Restaurant\_latitude          &45593 &non-null  &float64\\
    Restaurant\_longitude         &45593 &non-null  &float64\\
    Delivery\_location\_latitude   &45593 &non-null  &float64\\
    Delivery\_location\_longitude  &45593 &non-null  &float64\\
    Order\_Date                   &45593 &non-null  &object \\
    Time\_Orderd                  &43862 &non-null  &object \\
    Time\_Order\_picked            &45593 &non-null  &object \\
    Weatherconditions            &45593 &non-null  &object \\
    Road\_traffic\_density         &44992 &non-null  &object \\
    Vehicle\_condition            &45593 &non-null  &int64  \\
    Type\_of\_order                &45593 &non-null  &object \\
    Type\_of\_vehicle              &45593 &non-null  &object \\
    multiple\_deliveries          &44600 &non-null  &float64\\
    Festival                     &45365 &non-null  &object \\
    City                         &44393 &non-null  &object \\
    Time\_taken(min)              &45593 &non-null  &object \\
    \bottomrule[1.5pt]
    \end{tabular}
    \vspace{\baselineskip}
    \end{table}

    
我们发现数据集中存在大量缺失值，我们对数值类缺失值采用均值填充的方法去除缺失值，对于布尔类型数据使用众数填充，对于字符类变量保留其缺失值Unknown。

数据集的缺失值与重复值统计如表~\ref{tab:table2}~所示
\begin{table}[htbp]
    \caption{训练数据集数据统计}\label{tab:table2}
    \vspace{0.5em}\centering\wuhao
    \begin{tabular}{ll}
    \toprule[1.5pt]
    Column & NULL\_num\\
    \midrule[1pt]
    ID & 0\\
    Delivery\_person\_ID           &0 \\
    Delivery\_person\_Age          &1854\\
    Delivery\_person\_Ratings      &1908\\
    Restaurant\_latitude          &0\\
    Restaurant\_longitude         &0\\
    Delivery\_location\_latitude   &0\\
    Delivery\_location\_longitude  &0\\
    Order\_Date                   &0 \\
    Time\_Orderd                  &1731 \\
    Time\_Order\_picked            &0 \\
    Weatherconditions            &0 \\
    Road\_traffic\_density         &601 \\
    Vehicle\_condition            &0 \\
    Type\_of\_order                &0 \\
    Type\_of\_vehicle              &0\\
    multipl\_deliveries          &993\\
    Festival                     &228 \\
    City                         &1200 \\
    Time\_taken(min)              &0 \\
    \bottomrule[1.5pt]
    \end{tabular}
    \vspace{\baselineskip}
\end{table}
接下来需要将餐厅的经纬度坐标和送餐点的经纬度坐标通过半正矢公式转化为两地点之间的距离distance，用来作为机器学习的模型
同时使用独热编码将是非是节日处理为布尔变量参数化。\\

\subsection{参数相关性分析}
针对整个饿了么项目能够获得的数据，我们从数据集中取出了距离、外卖员年龄、外卖员评分、外卖员驾驶车辆类型，运送食物类型、所在城市、交付次数这7个参数，我们需要对其与运送
时间的相关性分析来判断这些参数是否对送餐时间存在实际影响。
\begin{figure}[htbp]
    \centering
    \includegraphics[width=1\textwidth]{distance}
    \caption{送餐时间与距离的线性关系}\label{fig:distance}
    \vspace{\baselineskip}
    \end{figure}
\begin{figure}[htbp]
        \centering
        \includegraphics[width=1\textwidth]{age}
        \caption{外卖员年龄与送餐时间的关系}\label{fig:age}
        \vspace{\baselineskip}
        \end{figure}
\begin{figure}[htbp]
    \centering
    \includegraphics[width=1\textwidth]{festival}
    \caption{节假日与城市与送餐时间的关系}\label{fig:fes}
    \vspace{\baselineskip}
\end{figure}
\begin{figure}[htbp]
    \centering
    \includegraphics[width=1\textwidth]{rating}
    \caption{外卖员评分与送餐时间的关系}\label{fig:rate}
    \vspace{\baselineskip}
    \end{figure}
\begin{figure}[htbp]
    \centering
    \includegraphics[width=1\textwidth]{type}
    \caption{送餐载具与送餐类型与送餐时间的关系}\label{fig:type}
    \vspace{\baselineskip}
\end{figure}
\begin{figure}[htbp]
    \centering
    \includegraphics[width=1\textwidth]{mul}
    \caption{多次交付与送餐时间的关系}\label{fig:mul}
    \vspace{\baselineskip}
    \end{figure}

因此，根据分析，对送餐时间影响较大的特征有以下四点。
\begin{itemize}
    \item 外卖员年龄
    \item 外卖员评分
    \item 餐厅和送餐地点之间的距离
    \item 是否处于拥挤时间段（节假日或高峰期）
\end{itemize}
\subsection{机器模型训练}
本项目选择了LSTM长短时间记忆神经模型，将数据集按9：1的比例拆分为训练集和验证集，将年龄、评分
、距离、节假日等参数作为输入参数，建立了由2层LSTM神经层、2层全连接层构成的神经学习网络。第一层
LSTM层有128个输出单元并返回完整输出序列，第二层LSTM有64个输出单元，不返回完整输出序列仅返回最后一个时间步输出
\begin{lstlisting}
    x = np.array(data[["Delivery_person_Age",
    "Delivery_person_Ratings",
    "distance",
    "Festival_Yes "]])
    y = np.array(data[["Time_taken(min)"]])
    xtrain, xtest, ytrain, ytest = train_test_split(x, y,
    test_size=0.10,
    random_state=42)
    # 创建LSTM神经网络模型
    model = Sequential()
    model.add(LSTM(128, return_sequences=True, input_shape=(xtrain.shape[1], 1)))
    model.add(LSTM(64, return_sequences=False))
    model.add(Dense(25))
    model.add(Dense(1))
\end{lstlisting}
我们尝试了0.1、0.01、0.001三种学习率，当学习率为0.001的时候，模型不会出现过拟合或无法收敛的情况。
对于损失函数的选择，我们选择了均方差损失、平均绝对误差损失、Huber Loss等函数，由于数据集可能存在离群值
我们选择了平均绝对误差函数，该函数对离群值不敏感，该神经网络不会因为离群值而导致模型不收敛。
我们将学习次数设置为20。最终得到的模型损失值在5左右
\begin{lstlisting}
    # 将NumPy数组转换为Tensor
    xtrain = tf.convert_to_tensor(xtrain, dtype=tf.float32)
    ytrain = tf.convert_to_tensor(ytrain, dtype=tf.float32)
    # 编译并训练模型
    optimizer = Adam(learning_rate=0.001)
    loss_function = MeanAbsoluteError()
    model.compile(optimizer=optimizer , loss=loss_function)
    model.fit(xtrain, ytrain, batch_size=1, epochs=20)
\end{lstlisting}
\section{项目部署}
\subsection{工作脚本设计}
我们通过如下脚本在controler层进行调用，只需要输入外卖员年龄、评分、两地距离，以及判断现在是否处于外卖高峰时间段，
即可返回机器学习模型预测的外卖送达时间。
\begin{lstlisting}
    import numpy as np
    import sys
    from keras.models import load_model
    #加载模型
    model = load_model('my_model.h5')
    
    def main():
        a = int(sys.argv[1])
        b = float(sys.argv[2])
        c = int(sys.argv[3])
        d = bool(sys.argv[4])
        #传入学习参数
        features = np.array([[a,b,c,d]])
        #将结果打印到输出流
        print(model.predict(features))
    
    if __name__=="__main__":
        main()
\end{lstlisting}
\subsection{Controler层设计}
然后通过添加控制流代码，当点餐结束后在数据库中读取相关数据传入python脚本，再将返回的数据发送给前端显示在网页上
\begin{lstlisting}
    public String callPythonScript(Integer age, Double rating, Double distance, Integer isPeak) {
        String res = null;
    try {
            ProcessBuilder processBuilder = new ProcessBuilder("python",
                    "D:\\project-summer-2023\\Mlearn\\ML.py", age.toString(), rating.toString(), distance.toString(), isPeak.toString());
            Process process = processBuilder.start();

            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                res = line;
            }
    }
    }
\end{lstlisting}
实现效果如图~\ref{fig:final}所示
\begin{figure}[htbp]
    \centering
    \includegraphics[width=0.5\textwidth]{final}
    \caption{实际项目结果}\label{fig:final}
    \vspace{\baselineskip}
\end{figure}
\section{项目团队和分工}
张志强和郭岩负责机器学习方向调研和数据集查找。池永杰负责数据集分析、机器学习模型训练、调用脚本代码撰写以及相关部分的实验报告撰写工作。
王英文博和苟杰恺负责前端设计以及数据库设计
